﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Media.Imaging;

using System.IO.IsolatedStorage;
using System.IO;
using System.Text;
using System.Windows.Media.Effects;

using Algorithms;
using GameEngine.Weather;

namespace GameEngine
{
    /// <remarks>
    ///     Represents a gameboard(screen) made of tiles.
    /// </remarks>
    public class GameGrid
    {

        /// <summary>
        ///     Size of the individual tile.
        /// </summary>
        private IntSize _tileSize = new IntSize();
        /// <summary>
        ///     Gets the size of the tile in pixels.
        /// </summary>
        public IntSize TileSize
        {
            get
            {
                return this._tileSize;
            }
        }

        /// <summary>
        ///     Size of the board in number of tiles.
        /// </summary>
        private IntSize _boardSize = new IntSize();
        /// <summary>
        ///     Gets the size of the board in number of tiles.
        /// </summary>
        public IntSize BoardSize
        {
            get
            {
                return this._boardSize;
            }
        }

        /// <summary>
        ///     Size of the board in pixels.
        /// </summary>
        private IntSize _boardSizePixels = new IntSize();
        /// <summary>
        ///     Gets the size of the board in pixels.
        /// </summary>
        public IntSize BoardSizePixels
        {
            get
            {
                return this._boardSizePixels;
            }
        }

        /// <summary>
        ///     Color of the GameGrid background.
        /// </summary>
        private Color _backgroundColor = Colors.Black;
        /// <summary>
        ///     Sets the color of the GameGrid background.
        /// </summary>
        public Color BackgroundColor
        {
            set
            {
                _backgroundColor = value;
            }
        }

        /// <summary>
        ///     Array of tiles from which the grid is made of.
        /// </summary>
        private Tile[,] _tileArray = null;
        /// <summary>
        ///     List of all tiles with animation.
        /// </summary>
        public List<Tile> _animatedTiles = null;
        /// <summary>
        ///     List of objects currently on the grid(not necessarily visible objects). 
        ///     Only these objects get drawed/moved etc.
        /// </summary>
        private List<GameObject> _objectsOnGrid = null;
        /// <summary>
        ///     Player on the grid(only one instance allowed at the same time).
        /// </summary>
        private PlayerCharacter _player = null;
        /// <summary>
        ///     The bitmap used to render the tiles on the gameboard gameboard.
        /// </summary>
        private WriteableBitmap _screen = null;
        /// <summary>
        ///     The bitmap used to render the gameboard with the viewport.
        /// </summary>
        private WriteableBitmap _final = null;
        /// <summary>
        ///     Viewport for this board. If null nothing is displayed.
        /// </summary>
        private Viewport _viewport = null;

        /* 
         * to be reworked with viewport mask
        private Rect _viewportSrc;
        private Rect _viewportDst;
        private WriteableBitmap _viewportMask;
         * */

        /// <summary>
        ///     Upper left tile of the visible part of board.
        /// </summary>
        private IntPoint _startTile;
        /// <summary>
        ///     Lower right tile of the visible part of board. 
        /// </summary>
        private IntPoint _endTile;
        /// <summary>
        ///     Offset of the map from the upper left corner of the image - used for rendering and mouse input.
        /// </summary>
        private IntPoint _offset = new IntPoint(0,0);
        /// <summary>
        ///     Source rectangle for blitting the tile bitmap to the gameboard bitmap.
        /// </summary>
        private Rect _tileSrc;
        /// <summary>
        ///     Destination rectangle for blitting the tile bitmap to the gameboard bitmap.    
        /// </summary>
        private Rect _tileDst;
        /// <summary>
        ///     The tile bitmap.    
        /// </summary>
        private WriteableBitmap _buffer;
        /// <summary>
        ///     Parthfinder used for ground objects.
        /// </summary>
        private PathFinder _pathFinderGround;
        /// <summary>
        ///     Parthfinder used for aerial objects.
        /// </summary>
        private PathFinder _pathFinderAir;
        /// <summary>
        ///     Map used for pathfinding for ground objects.
        /// </summary>
        private byte[,] _pathMapGround;
        /// <summary>
        ///     Map used for pathfinding for aerial objects.
        /// </summary>
        private byte[,] _pathMapAir;
        /// <summary>
        ///     Weather effect to be used on this map.
        /// </summary>
        private WeatherEffect _weatherEffect = null;
        /// <summary>
        ///     Sets the weather effect for this map.
        /// </summary>
        public WeatherEffect WeatherEffect
        {
            set
            {
                this._weatherEffect = value;
            }
        }


        /// <summary>
        ///     Constructor takes the size of one tile in pixels, and the size of the board in tiles.
        /// </summary>
        /// <param name="tileSize">size of one tile in pixels</param>
        /// <param name="boardSize">size of the board in number of tiles</param>
        public GameGrid(IntSize tileSize, IntSize boardSize)
        {
            _tileSize.Height = tileSize.Height;
            _tileSize.Width = tileSize.Width;

            _boardSize.Height = boardSize.Height;
            _boardSize.Width = boardSize.Width;

            _boardSizePixels.Height = _boardSize.Height * _tileSize.Height;
            _boardSizePixels.Width = _boardSize.Width * _tileSize.Width;

            //sets the default area-whole board-for update
            _startTile = new IntPoint(0, 0);
            _endTile = new IntPoint(_boardSize.Width, _boardSize.Height);

            //sets the source rect-0,0
            _tileSrc = new Rect(0, 0, _tileSize.Width, _tileSize.Height);
            //sets the destination rect-0,0
            _tileDst = new Rect(0, 0, _tileSize.Width, _tileSize.Height);
            //buffer with size of the tile
            _buffer = new WriteableBitmap(_tileSize.Width, _tileSize.Height);
            
            //list of objects
            _objectsOnGrid = new List<GameObject>();
            //list of animated tiles
            _animatedTiles = new List<Tile>();

            /*
             * to be reworked with viewport mask
            _final = new WriteableBitmap(_boardSizePixels.Width, _boardSizePixels.Height);
             */


            //creates new map for pathfinding
            _pathMapGround = new byte[_boardSize.Width, _boardSize.Height];
            _pathMapAir = new byte[_boardSize.Width, _boardSize.Height];

            _tileArray = new Tile[_boardSize.Width, _boardSize.Height];
            for (int y = 0; y < _boardSize.Height; y++)
            {
                for (int x = 0; x < _boardSize.Width; x++)
                {
                    _tileArray[x,y] = new Tile(_tileSize);
                    //initializes the map for pathfinding
                    _pathMapGround[x, y] = 0;
                    _pathMapAir[x, y] = 0;
                }
            }

            //creates new pathfinder and initializes it with the map
            _pathFinderGround = new PathFinder(_pathMapGround);
            _pathFinderAir = new PathFinder(_pathMapAir);


            //temporary-to be moved/removed
            if (_player != null)
                _player.PathFinderGround = _pathFinderGround;

            //we use diagonals
            _pathFinderGround.Diagonals = true;
            _pathFinderAir.Diagonals = true;
            //euclidean heuristic
            _pathFinderGround.Formula = PathFinder.HeuristicFormula.Euclidean;
            _pathFinderAir.Formula = PathFinder.HeuristicFormula.Euclidean;
            // use tie breaker
            _pathFinderGround.TieBreaker = true;
            _pathFinderAir.TieBreaker = true;
        }

        
        /// <summary>
        ///     Sets effect for the tile.
        /// </summary>
        /// <param name="tile">Tile for which the animation is being set.</param>
        /// <param name="sheet">Effect being set.</param>
        public void SetTileEffect(IntPoint tile, Effect effect, TileLayers layer)
        {
            _tileArray[tile.X, tile.Y].SetEffect(effect, layer); 
        }


        /// <summary>
        ///     Sets effect for a rectangular region of tiles.
        /// </summary>
        /// <param name="tile">Region for which the effect is being set.</param>
        /// <param name="sheet">Effect being set.</param>
        public void SetTileEffect(Rect region, Effect effect, TileLayers layer)
        {
            for (int y = (int)region.Y; y < (int)(region.Y + region.Height); y++)
            {
                for (int x = (int)region.X; x < (int)(region.X + region.Width); x++)
                {
                    if (x < _boardSize.Width && y < _boardSize.Height)
                        _tileArray[x, y].SetEffect(effect, layer);
                }
            }
        }


        /// <summary>
        ///     Sets animation for the tile.
        /// </summary>
        /// <param name="tile">Tile for which the animation is set.</param>
        /// <param name="sheet">Animation being set.</param>
        public void SetTileAnimation(IntPoint tile, Spritesheet sheet, double frameLength)
        {
            //Spritesheet temp = new Spritesheet(sheet);
            //Spritesheet temp = sheet;
            Spritesheet temp = Spritesheet.Clone(sheet);
            _tileArray[tile.X, tile.Y].Animation = temp;
            _tileArray[tile.X, tile.Y].FrameLength = frameLength;
            this._animatedTiles.Add(_tileArray[tile.X, tile.Y]);
        }


        /// <summary>
        ///     Sets animation for a rectangular region of tiles.
        /// </summary>
        /// <param name="region">Region for which the animation is set. </param>
        /// <param name="sheet">Animation being set.</param>
        public void SetTileAnimation(Rect region, Spritesheet sheet, double frameLength)
        {
            for (int y = (int)region.Y; y < (int)(region.Y + region.Height); y++)
            {
                for (int x = (int)region.X; x < (int)(region.X + region.Width); x++)
                {
                    //Spritesheet temp = new Spritesheet(sheet);
                    //Spritesheet temp = sheet;
                    Spritesheet temp = Spritesheet.Clone(sheet);
                    _tileArray[x, y].Animation = temp;
                    _tileArray[x, y].FrameLength = frameLength;
                    this._animatedTiles.Add(_tileArray[x, y]);
                }
            }
        }


        /// <summary>
        ///     Sets the viewport.
        /// </summary>
        /// <param name="viewPort">The viewport being set.</param>
        public void SetViewport(Viewport viewPort)
        {
            _viewport = viewPort;
            _viewport.ViewportChanged+=new ViewportChangedHandler(ViewportChanged);
            RecalculateViewport();
        }


        /// <summary>
        ///     Recalculates viewport position.
        /// </summary>
        private void RecalculateViewport()
        {
            if (_viewport != null)
            {
                //sets the viewport dimensions
                this._viewport.GetViewportDimensions(this._boardSizePixels, out this._startTile, out this._endTile, out this._offset); 

                //creates the bitmap for the visible part of the map(with size of the viewport)
                _screen = new WriteableBitmap(_viewport._size.Width, _viewport._size.Height);

                //set to limit the search to the visible part of the map-experimental
                //_pathFinderGround.SearchLimit = viewPort._size.Height*viewPort._size.Width;

                /*
                 * to be reworked with viewport mask
                //sets the source region for the viewport
                _viewportSrc = new Rect(0, 0, _viewport._size.Width * _tileSize.Width, _viewport._size.Height * _tileSize.Height);
                //sets the destination region for the viewport
                _viewportDst = new Rect(0, 0, _viewport._size.Width * _tileSize.Width, _viewport._size.Height * _tileSize.Height);
                //mask for the viewport
                _viewportMask = _viewport.GetViewport(_tileSize);
                 * */
            }
        }


        /// <summary>
        ///     Recalculates viewport position when wiewport changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ViewportChanged(object sender, ViewportChangedArgs e)
        {
            RecalculateViewport();
        }


        /// <summary>
        ///     Calculates AI moves.
        /// </summary>
        /// <param name="elapsedTime">Time elapsed from previous call.</param>
        public void ComputeAI(TimeSpan elapsedTime)
        { 
        }


        /// <summary>
        ///     Calls Move method for every animated/moving object.
        /// </summary>
        /// <param name="elapsedTime">Time elapsed from previous call.</param>
        public void Move(TimeSpan elapsedTime)
        {
            foreach (AnimatedGameObject item in this._objectsOnGrid)
            {
                item.Move(elapsedTime, this._tileSize);
            }
        }


        /// <summary>
        ///     Calls Animate method for every animated object and animated tiles.
        /// </summary>
        /// <param name="elapsedTime">Time elapsed from previous call.</param>
        public void Animate(TimeSpan elapsedTime)
        {
            foreach (AnimatedGameObject item in this._objectsOnGrid)
            {
                item.Animate(elapsedTime);
            }

            foreach (Tile item in this._animatedTiles)
            {
                item.Animate(elapsedTime);
            }
        }


        /// <summary>
        ///     Updates the current weather effect.
        /// </summary>
        /// <param name="?">Time elapsed from previous call.</param>
        public void UpdateWeather(TimeSpan elapsedTime)
        {
            if (this._weatherEffect != null)
                this._weatherEffect.Update(elapsedTime);
        }


        /// <summary>
        ///     Updates the visual content and returns the bitmap representing the new content.
        /// </summary>
        /// <returns>The new bitmap.</returns>
        public WriteableBitmap Update()
        {
            if (_viewport == null)
                return null;

            //to be reworked - use background color set by the user/designer
            _screen.Clear(this._backgroundColor);

            for (int y = _startTile.Y, b = _offset.Y, d = _startTile.Y / _tileSize.Height; y <= _endTile.Y; y += _tileSize.Height, b += _tileSize.Height)
            {
                for (int x = _startTile.X, a = _offset.X, c = _startTile.X / _tileSize.Width; x <= _endTile.X; x += _tileSize.Width, a += _tileSize.Width)
                {
                    if (c >= this._boardSize.Width || d >= this._boardSize.Height)
                        break;
                    _buffer = _tileArray[c, d].GetTileImage();
                    _tileDst.X = a;
                    _tileDst.Y = b;
                    _screen.Blit(_tileDst, _buffer, _tileSrc, WriteableBitmapExtensions.BlendMode.None);
                    c++;
                }
                if (d >= this._boardSize.Height)
                    break;
                if (y >= 0)
                    d++;
            }


            foreach (GameObject item in this._objectsOnGrid)
            {
                //test if the position is inside the viewport-later
                _buffer = item.Update();
                if (_buffer != null)
                {
                    //to be optimized
                    Rect Dst = new Rect(item.Position.X - _startTile.X + _offset.X, item.Position.Y - _startTile.Y + _offset.Y, _buffer.PixelWidth, _buffer.PixelHeight);
                    Rect Src = new Rect(0, 0, _buffer.PixelWidth, _buffer.PixelHeight);
                    _screen.Blit(Dst, _buffer, Src, WriteableBitmapExtensions.BlendMode.AlphaBlend);
                }
            }


            //Applies the weather effect
            if (_weatherEffect != null)
            {
                //WriteableBitmap temp = _weatherEffect.GetEffect(_startTile.X - _offset.X, _startTile.Y - _offset.Y, this._viewport._size);
                WriteableBitmap temp = _weatherEffect.GetEffect();
                if (temp != null)
                {
                    Rect Dst = new Rect(0, 0, this._viewport._size.Width, this._viewport._size.Height);
                    Rect Src = new Rect(0, 0, this._viewport._size.Width, this._viewport._size.Height);

                    _screen.Blit(Dst, temp, Src, WriteableBitmapExtensions.BlendMode.AlphaBlend);
                }
            }


            /*
             * to be reworked with viewport mask
             *_screen.Blit(_viewportDst, _viewportMask, _viewportSrc, WriteableBitmapExtensions.BlendMode.Mask);
             */

            return _screen;
        }


        /// <summary>
        ///     Loads a selected tileset-<paramref>tileset</paramref> from file, and creates a gameboard.
        ///     Tiles are described in the array <paramref>tiles</paramref> like this {0,3} 0-1st row, 3-4th column
        /// </summary>
        /// <param name="tileset">
        ///     Path to the tileset.
        /// </param>
        /// <param name="tiles">
        ///     Array of numbers representing tiles from tileset. 
        /// </param>
        /// <param name="layer">
        ///     Background/foreground.
        /// </param>
        public void SetTileMap(string tileset, ref int[,] tiles, TileLayers layer)
        {            
            int i = 0;
            for (int y = 0; y < _boardSize.Height; y++)
            {
                for (int x = 0; x < _boardSize.Width; x++)
                {
                    if (i == tiles.GetLength(0))
                        break;
                    TranslateTransform translation = new TranslateTransform();
                    translation.X = -(tiles[i, 0] * _tileSize.Width);
                    translation.Y = -(tiles[i, 1] * _tileSize.Height);

                    //pathfinding for ground
                    _pathMapGround[x, y] = (byte)tiles[i, 2];
                    if (tiles[i, 2] == 0)
                        _tileArray[x, y].Pathable = false;
                    else
                        _tileArray[x, y].Pathable = true;

                    //pathfinding for air
                    _pathMapAir[x, y] = (byte)tiles[i, 3];
                    if (tiles[i, 3] == 0)
                        _tileArray[x, y].FlyPathable = false;
                    else
                        _tileArray[x, y].FlyPathable = true;

                    _tileArray[x, y].SetTileImage(tileset, translation, layer);
                    i++;
                }
            }
        }



        /// <summary>
        ///     Loads a selected tileset-<paramref>tileset</paramref> from file, and creates a gameboard.
        ///     Tiles are described in the array <paramref>tiles</paramref> like this {0,3} 0-1st row, 3-4th column
        /// </summary>
        /// <param name="tileset">
        ///     Bitmap image of the tileset.
        /// </param>
        /// <param name="tiles">
        ///     Array of numbers representing tiles from tileset. 
        /// </param>
        /// <param name="layer">
        ///     Background/foreground.
        /// </param>
        public void SetTileMap(BitmapImage tileset, ref int[,] tiles, TileLayers layer)
        {
            int i = 0;
            for (int y = 0; y < _boardSize.Height; y++)
            {
                for (int x = 0; x < _boardSize.Width; x++)
                {
                    if (i == tiles.GetLength(0))
                        break;
                    TranslateTransform translation = new TranslateTransform();
                    translation.X = -(tiles[i, 0] * _tileSize.Width);
                    translation.Y = -(tiles[i, 1] * _tileSize.Height);

                    //pathfinding for ground
                    _pathMapGround[x, y] = (byte)tiles[i, 2];
                    if (tiles[i, 2] == 0)
                        _tileArray[x, y].Pathable = false;
                    else
                        _tileArray[x, y].Pathable = true;

                    //pathfinding for air
                    _pathMapAir[x, y] = (byte)tiles[i, 3];
                    if (tiles[i, 3] == 0)
                        _tileArray[x, y].FlyPathable = false;
                    else
                        _tileArray[x, y].FlyPathable = true;

                    _tileArray[x, y].SetTileImage(tileset, translation, layer);
                    i++;
                }
            }
        }


        /// <summary>
        ///     Changes selected tile's foreground or background.
        /// </summary>
        /// <param name="tile">Tile to be changed.</param>
        /// <param name="tileset">Path to the tileset./// </param>
        /// <param name="coord">Which tile from tileset to use.</param>
        /// <param name="layer">Background/foreground.</param>
        public void SetTile(IntPoint tile, string tileset, IntPoint coord, TileLayers layer)
        {
            TranslateTransform translation = new TranslateTransform();
            translation.X = -(coord.X * _tileSize.Width);
            translation.Y = -(coord.Y * _tileSize.Height);
            _tileArray[tile.X, tile.Y].SetTileImage(tileset, translation, layer);
        }


        /// <summary>
        ///     Sets the whole gamegrid/gameboard blank.      
        /// </summary>
        public void SetBlank()
        {
            foreach (Tile item in _tileArray)
            {
                item.SetBlankTile(TileLayers.foreground);
                item.SetBlankTile(TileLayers.background);
            }
        }


        /// <summary>
        ///     Sets the whole gamegrid/gameboard transparent.      
        /// </summary>
        public void SetTransparent()
        {
            foreach (Tile item in _tileArray)
            {
                item.SetTransparentTile(TileLayers.foreground);
                item.SetTransparentTile(TileLayers.background);
            }
        }


        /// <summary>
        ///     Sets which layer is shown for each tile.
        /// </summary>
        public void SetShownLayer(TileLayers layer)
        {
            foreach (Tile item in this._tileArray)
            {
                item.Show = layer;
            }
        }


        /// <summary>
        ///     Sets the function for the event.
        /// </summary>
        /// <param name="tile">For which tile we're setting the trigger.</param>
        /// <param name="type">Type of trigger we're setting.</param>
        /// <param name="onTriggerFunc">Delegate to be called on event.</param>
        public void SetTrigger(IntPoint tile, TileTriggerTypes type, TriggerHandler onTriggerFunc)
        {
            switch (type)
            {
                case TileTriggerTypes.onclick:
                    this._tileArray[tile.X, tile.Y].ClickTrigger += onTriggerFunc;
                    break;
                case TileTriggerTypes.onflyover:
                    this._tileArray[tile.X, tile.Y].FlyOverTrigger += onTriggerFunc;
                    break;
                case TileTriggerTypes.onwalkover:
                    this._tileArray[tile.X, tile.Y].WalkOverTrigger += onTriggerFunc;
                    break;
            }
        }


        /// <summary>
        ///     Loads the map's visual and pathfinding data from the specified file.
        ///     Map editor uses this.
        /// </summary>
        /// <param name="mapFile">Stream to load from.</param>
        public static GameGrid LoadMapFromFile(FileStream mapFile)
        {
            System.IO.StreamReader sreader = new System.IO.StreamReader(mapFile);
            string line;
            string[] split;
            IntSize boardSize = new IntSize();
            IntSize tileSize = new IntSize();
            IntPoint coord = new IntPoint();
            IntPoint tile = new IntPoint();
            bool pathable;
            bool flypathable;
            List<Spritesheet> spritesheets = new List<Spritesheet>();
            List<String> sources = new List<string>();

            //note-no validation used- we trust the user not to change the file


            do
            {
                line = sreader.ReadLine();
                //loads spritesheets
                if (line[0] != ':')
                {
                    //loads 1st kind of animation-one-file
                    if (line[1] == '1')
                    {
                        IntSize spriteSize = new IntSize(0, 0);
                        string source;
                        int framecount;
                        IntPoint to = new IntPoint(0,0);
                        IntPoint from = new IntPoint(0, 0);
                        Spritesheet temp;

                        //loads the size
                        line = sreader.ReadLine();
                        if (line != null)
                        {
                            split = line.Split(new Char[] { ' ' });
                            int.TryParse(split[0], out spriteSize.Width);
                            int.TryParse(split[1], out spriteSize.Height);
                        }
                        //loads the source
                        source = sreader.ReadLine();
                        //loads the framecount
                        line = sreader.ReadLine();
                        int.TryParse(line, out framecount);
                        //loads the from point
                        line = sreader.ReadLine();
                        if (line != null)
                        {
                            split = line.Split(new Char[] { ' ' });
                            int.TryParse(split[0], out from.X);
                            int.TryParse(split[1], out from.Y);
                        }
                        //loads the to point
                        line = sreader.ReadLine();
                        if (line != null)
                        {
                            split = line.Split(new Char[] { ' ' });
                            int.TryParse(split[0], out to.X);
                            int.TryParse(split[1], out to.Y);
                        }
                        //creates the animation
                        temp = new Spritesheet(source, spriteSize, framecount, from, to);
                        spritesheets.Add(temp);
                        //reads the trailing question mark
                        line = sreader.ReadLine();
                    }
                    //loads 2nd kind of animation-multi-file
                    else if (line[1] == '2')
                    {
                        IntSize spriteSize = new IntSize(0, 0);
                        List<String> spritesources = new List<string>();
                        Spritesheet temp;

                        //loads the size
                        line = sreader.ReadLine();
                        if (line != null)
                        {
                            split = line.Split(new Char[] { ' ' });
                            int.TryParse(split[0], out spriteSize.Width);
                            int.TryParse(split[1], out spriteSize.Height);
                        }
                        //reads the sources and the trailing question mark
                        do
                        {
                            line = sreader.ReadLine();
                            if (line[0] != '?')
                            {
                                spritesources.Add(line);
                            }
                        }
                        while (line[0] != '?');

                        string[] spritesourcesarray= new string[spritesources.Count];
                        int i = 0;
                        foreach (String item in spritesources)
                        {
                            spritesourcesarray[i] = item;
                            i++;
                        }
                        //creates the animation
                        temp = new Spritesheet(spritesourcesarray, spriteSize);
                        spritesheets.Add(temp);
                    }
                }
            }
            while (line[0] != ':');
            
            //reads the sources
            do
            {
                line = sreader.ReadLine();
                if (line[0] != ':')
                {
                    sources.Add(line);
                }
            }
            while (line[0] != ':');

            //reads the boardsize
            line = sreader.ReadLine();
            if (line != null)
            {
                split = line.Split(new Char[] { ' ' });
                int.TryParse(split[0], out boardSize.Width);
                int.TryParse(split[1], out boardSize.Height);
            }

            //reads the tilesize
            line = sreader.ReadLine();
            if (line != null)
            {
                split = line.Split(new Char[] { ' ' });
                int.TryParse(split[0], out tileSize.Width);
                int.TryParse(split[1], out tileSize.Height);
            }

            //creates the new grid
            GameGrid newGrid = new GameGrid(tileSize, boardSize);

            //loads the data for each tile
            for (int x = 0; x < boardSize.Width; x++)
            {
                for (int y = 0; y < boardSize.Height; y++)
                {
                    line = sreader.ReadLine();
                    if (line != null)
                    {
                        split = line.Split(new Char[] { ' ' });

                        tile.X = x;
                        tile.Y = y;

                        if (!(split[0].Length == 1))
                        {

                            int.TryParse(split[1], out coord.X);
                            int.TryParse(split[2], out coord.Y);
                            //must invert sign to work like a translation
                            TranslateTransform tt = new TranslateTransform();
                            tt.X = coord.X;
                            tt.Y = coord.Y;

                            //removes the leading colon
                            split[0] = split[0].Remove(0,1);
                            int index;
                            //parses the index to sources
                            int.TryParse(split[0], out index);

                            newGrid._tileArray[x, y].SetTileImage(sources[index], tt, TileLayers.foreground);
                        }
                        else if (split[0] == "B")
                        {
                            newGrid._tileArray[x, y].SetBlankTile(TileLayers.foreground);
                        }
                        else if (split[0] == "T")
                        {
                            newGrid._tileArray[x, y].SetTransparentTile(TileLayers.foreground);
                        }
                        else if (split[0] == "S")
                        {
                            int index;
                            //we get the index
                            int.TryParse(split[1], out index);
                            //and set the animation
                            newGrid._tileArray[x, y].Animation = spritesheets[index];
                            newGrid._tileArray[x, y].FrameLength = 0.1;
                            newGrid._animatedTiles.Add(newGrid._tileArray[x, y]);
                        }


                        if (!(split[3].Length == 1))
                        {
                            int.TryParse(split[4], out coord.X);
                            int.TryParse(split[5], out coord.Y);
                            //must invert sign to work like a translation
                            TranslateTransform tt = new TranslateTransform();
                            tt.X = coord.X;
                            tt.Y = coord.Y;

                            //removes the leading colon
                            split[3] = split[3].Remove(0, 1);
                            int index;
                            //parses the index to sources
                            int.TryParse(split[3], out index);

                            newGrid._tileArray[x, y].SetTileImage(sources[index], tt, TileLayers.background);
                        }
                        else if (split[3]=="B")
                        {
                            newGrid._tileArray[x, y].SetBlankTile(TileLayers.background);
                        }
                        else if (split[3] == "T")
                        {
                            newGrid._tileArray[x, y].SetTransparentTile(TileLayers.background);
                        }
                        else if (split[3] == "S")
                        {
                            //we dont have to do anything here
                        }

                        //sets pathing information-ground
                        if (split[6] == "T")
                            pathable = true;
                        else
                            pathable = false;
                        newGrid._tileArray[x, y].Pathable = pathable;
                        if (pathable)
                            newGrid._pathMapGround[x, y] = 1;
                        else
                            newGrid._pathMapGround[x, y] = 0;

                        //sets pathing information-air
                        if (split[7] == "T")
                            flypathable = true;
                        else
                            flypathable = false;
                        newGrid._tileArray[x, y].FlyPathable = flypathable;
                        if (pathable)
                            newGrid._pathMapAir[x, y] = 1;
                        else
                            newGrid._pathMapAir[x, y] = 0;
                    }
                }
            }

            return newGrid;
        }


        /// <summary>
        ///     Saves the map's visual and pathfinding data to the specified file.
        ///     Map editor uses this.
        /// </summary>
        /// <param name="mapFile">Stream to save to.</param>
        public void SaveMapToFile(System.IO.Stream mapFile)
        {
            StringBuilder data = new StringBuilder();

            //saves the size of the map
            data.Append(this._boardSize.Width + " " + this._boardSize.Height + "\r\n");
            //saves the size of one tile
            data.Append(this._tileSize.Width + " " + this._tileSize.Height + "\r\n");
            //additional data?

            List<String> sources = new List<string>();
            List<String> spritesources = new List<string>();
            //saves data for each tile
            for (int x = 0; x < this._boardSize.Width; x++)
            {
                for (int y = 0; y < this._boardSize.Height; y++)
                {
                    //for animated tiles
                    if (this._tileArray[x, y].Animation != null && !spritesources.Contains(this._tileArray[x, y].Animation.StringRepresentation()))
                    {
                        spritesources.Add(this._tileArray[x, y].Animation.StringRepresentation());
                    }
                    else
                    {
                        if ((this._tileArray[x, y]._backgroundSource != null) && !sources.Contains(this._tileArray[x, y]._backgroundSource))
                        {
                            sources.Add(this._tileArray[x, y]._backgroundSource);
                        }
                        if ((this._tileArray[x, y]._foregroundSource != null) && !sources.Contains(this._tileArray[x, y]._foregroundSource))
                        {
                            sources.Add(this._tileArray[x, y]._foregroundSource);
                        }
                    }

                    String temp = (this._tileArray[x, y].StringRepresentation() + Environment.NewLine);

                    if (this._tileArray[x, y].Animation == null)
                    {
                        //replace the source path with :number
                        foreach (string item in sources)
                        {
                            temp = temp.Replace(item, ":" + sources.IndexOf(item));
                        }
                    }
                    else
                    {
                        //replace the source path with :number
                        foreach (string item in spritesources)
                        {
                            temp = temp.Replace(item, spritesources.IndexOf(item).ToString());
                        }
                    }

                    data.Append(temp);
                }
            }

            System.IO.StreamWriter swriter = new System.IO.StreamWriter(mapFile);

            if (spritesources.Count > 0)
            {
                //writes the spritesheet data to file
                foreach (String item in spritesources)
                {
                    swriter.WriteLine(item);
                }
            }
            //writes the : separator
            swriter.WriteLine(":");
            
            if (sources.Count > 0)
            {
                //writes the paths to sources to the file
                foreach (String item in sources)
                    swriter.WriteLine(item);
            }
            //writes the : separator
            swriter.WriteLine(":");

            //and then the map data
            swriter.Write(data.ToString());
            swriter.Flush();
            swriter.Close();
        }


        /// <summary>
        ///     Adds player to the grid. Only one at a time allowed on one gamegrid. Uses base offset.
        /// </summary>
        /// <param name="character"> Player to be added.</param>
        /// <param name="tilePosition">Tile on which the player "sits".</param>
        /// <param name="position">Absolute position of the player on the map.</param>
        public void AddGameObject(PlayerCharacter player, IntPoint tilePosition, IntPoint position)
        {
            if (player == null)
                throw new System.ArgumentNullException();

            //saves the player instance-we send mouse input to it
            _player = player;

            this.AddGameObject((Character)player, tilePosition, position);
        }


        /// <summary>
        ///     Adds a character to the gamegrid(player, allied, enemy). Uses base offset.
        /// </summary>
        /// <param name="player">Player to be added.</param>
        /// <param name="tilePosition">Tile on which the character "sits".</param>
        /// <param name="position">Absolute position of the character on the map.</param>
        public void AddGameObject(Character character, IntPoint tilePosition, IntPoint position)
        {
            if (character == null)
                throw new System.ArgumentNullException();

            //sets the pathfinder to be used
            character.PathFinderGround = _pathFinderGround;
            //then calls the standard add gameobject method
            this.AddGameObject((AnimatedGameObject)character, tilePosition, position);
        }


        /// <summary>
        ///     Adds an AnimatedGameObject to the gamegrid. Uses base offset.
        /// </summary>
        /// <param name="player">AnimatedGameObject to be added.</param>
        /// <param name="tilePosition">Tile on which the animatedGameObject "sits".</param>
        /// <param name="position">Absolute position of the animatedGameObject on the map.</param>
        public void AddGameObject(AnimatedGameObject animatedGameObject, IntPoint tilePosition, IntPoint position)
        {
            if (animatedGameObject == null)
                throw new System.ArgumentNullException();

            //offsets the object
            position.X = position.X - animatedGameObject.BaseOffset.X;
            position.Y = position.Y - animatedGameObject.BaseOffset.Y;

            this.AddGameObject((GameObject)animatedGameObject, tilePosition, position);
        }


        /// <summary>
        ///     Adds a gameobject to the gamegrid.
        /// </summary>
        /// <param name="gameObject">Object to be added.</param>
        /// <param name="tilePosition">Tile on which the object "sits".</param>
        /// <param name="position">Absolute position of the GameObject on the map.</param>
        /// <exception cref="ArgumentException">Gets thrown when the desired position is outside the map.</exception>
        public void AddGameObject(GameObject gameObject, IntPoint tilePosition, IntPoint position)
        {
            if (gameObject == null)
                throw new System.ArgumentNullException();

            //if the desired position is out of bounds we throw an exception
            if (tilePosition.X > this._boardSize.Width || tilePosition.Y > this._boardSize.Height)
                throw new System.ArgumentException("Index x/y is greater than the size of the map.");

            gameObject.TilePosition = tilePosition;

            //if the desired position is out of bounds we throw an exception
            if (position.X > this._boardSizePixels.Width || position.Y > this._boardSizePixels.Height)
                throw new System.ArgumentException("Index x/y is greater than the size of the map.");

            gameObject.Position = position;

            //adds the object to the list
            this._objectsOnGrid.Add(gameObject);
        }


        #region MouseHandlers
        public void MouseEnter(IntPoint pos)
        { }


        public void MouseLeave(IntPoint pos)
        { }


        public void MouseLeftButtonDown(IntPoint pos)
        {
            IntPoint clickedPos = new IntPoint(0, 0);

            //translates the position to tiles
            clickedPos.X = (pos.X - _offset.X + _startTile.X);
            clickedPos.Y = (pos.Y - _offset.Y + _startTile.Y);

            if (clickedPos.X >= 0 && clickedPos.Y >= 0)
            {
                if (clickedPos.X < this._boardSizePixels.Width && clickedPos.Y < this._boardSizePixels.Height)
                {
                    //sends the mouse coordinates to the player character if present
                    if (_player != null)
                        _player.MouseInput(clickedPos, this._tileSize);
                    //fires the event on the particular tile
                    _tileArray[clickedPos.X / _tileSize.Width, clickedPos.Y / _tileSize.Height].OnClickTrigger(null);
                }
            }
        }


        public void MouseLeftButtonUp(IntPoint pos)
        { }


        public void MouseMove(IntPoint pos)
        {
            IntPoint clickedPos = new IntPoint(0, 0);

            //translates the position to tiles
            clickedPos.X = ((pos.X - _offset.X + _startTile.X));
            clickedPos.Y = ((pos.Y - _offset.Y + _startTile.Y));

            //MessageBox.Show("X=" + clickedPosition.X + " Y= " + clickedPosition.Y);

            //fire the event on the particular tile
            if (clickedPos.X >= 0 && clickedPos.Y >= 0)
            {
                if (clickedPos.X < this._boardSizePixels.Width && clickedPos.Y < this._boardSizePixels.Height)
                {
                    //sends the mouse coordinates to the player character if present
                    if (_player != null)
                        _player.MouseInput(clickedPos, this._tileSize);
                    //does NOT fire tile clicked event-fires it to enable faster paint-like map editig
                    //fires the event on the particular tile
                    _tileArray[clickedPos.X / _tileSize.Width, clickedPos.Y / _tileSize.Height].OnClickTrigger(null);
                }
            }
        }


        public void MouseWheel(IntPoint pos)
        { }

        #endregion
    }
}
